package resolver

import (
	"context"
	"github.com/hashicorp/consul/api"
	"github.com/pkg/errors"
	clientv3 "go.etcd.io/etcd/client/v3"
	"google.golang.org/grpc/resolver"
	"log"
	"strings"
	"sync"
)

func init() {
	resolver.Register(&builder{})
}

// schemeName for the urls
// All target URLs like 'consul://.../...' will be resolved by this resolver
const consulSchema = "consul"
const etcdSchema = "etcdv3"

// builder implements resolver.Builder and use for constructing all consul/etcd resolvers
type builder struct {
	schema string

	consul *api.Client
	etcd   *clientv3.Client

	cc         resolver.ClientConn
	serverList sync.Map
	cancelFunc context.CancelFunc
}

func (b *builder) Build(url resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {

	dsn := strings.Join([]string{url.URL.Scheme + ":/", url.URL.Host, url.URL.Path + "?" + url.URL.RawQuery}, "/")

	// 解析 url
	tgt, err := parseURL(dsn)
	if err != nil {
		return nil, errors.Wrap(err, "Wrong consul URL")
	}

	b.cc = cc

	switch tgt.scheme {

	case "etcd3":
		b.schema = etcdSchema
		b.etcd, err = b.newEtcdClient(tgt)

		prefix := "/" + tgt.scheme + "/" + url.Endpoint() + "/" // + tgt.Service

		err = b.cc.UpdateState(resolver.State{Addresses: b.getServices()})
		if err != nil {
			return nil, errors.Wrap(err, "Update Client Connect Failed")
		}
		// 创造一个用于取消操作的上下文， 可以通过 ctx.done 方法取消 ctx 来控制子协程的生命周期
		ctx, cancel := context.WithCancel(context.Background())
		b.cancelFunc = cancel

		// 监视前缀，修改变更的server
		go b.watchEtcdService(ctx, b.etcd, prefix, tgt)
		return b, nil

	case "consul":
		b.schema = consulSchema
		b.consul, err = b.newConsulClient(tgt)
		// 创造一个用于取消操作的上下文， 可以通过 ctx.done 方法取消 ctx 来控制子协程的生命周期
		ctx, cancel := context.WithCancel(context.Background())
		pipe := make(chan []string)
		b.cancelFunc = cancel
		go b.watchConsulService(ctx, b.consul.Health(), tgt, pipe)
		go b.populateEndpoints(ctx, b.cc, pipe)
		return b, nil

	default:
		return nil, errors.Wrap(err, "Unknown Error")
	}
}

// Scheme returns the scheme supported by this resolver.
// Scheme is defined at https://github.com/grpc/grpc/blob/master/doc/naming.md.
func (b *builder) Scheme() string {
	return b.schema
}

// Resolver.Close 方法
func (b *builder) Close() {
	log.Println("Close")
	b.cancelFunc()
}

// Resolver.ResolveNow 方法
func (b *builder) ResolveNow(rn resolver.ResolveNowOptions) {
	log.Println("ResolveNow")
}
